In [1]:
#include <thread>
#include <iostream>
#include <vector>

Use a mutex to make our Counter thread-safe

In the C++11 threading library, the mutexes are in the mutex header and the class representing a mutex is the std::mutex class. There are two important methods on a mutex: lock() and unlock(). As their names indicate, the first one enable a thread to obtain the lock and the second releases the lock. The lock() method is blocking. The thread will only return from the lock() method when the lock has been obtained. To make our Counter struct thread-safe, we have to add a std::mutex member to it and then to lock() / unlock() the mutex in every function of the object:

In [ ]:
struct Counter {
    int value;

    Counter() : value(0) {}

    void increment(){
        ++value;
    }

    void decrement(){
        if(value == 0){
            throw "Value cannot be less than 0";
        }
        --value;
    }
};

struct ConcurrentCounter {
    std::mutex mutex;
    Counter counter;

    void increment(){
        mutex.lock();
        try {
            counter.increment();
        } catch (std::string e){
            mutex.unlock();
            throw e;
        } 
        mutex.unlock();
    }

    void decrement(){
        mutex.lock();
        try {
            counter.decrement();
        } catch (std::string e){
            mutex.unlock();
            throw e;
        } 
        mutex.unlock();
    }
};

The code is not difficult but starts looking ugly. Now imagine you are in a function with 10 different exit points. You will have to call unlock() from each of these points and the probability that you will forget one is big. Even bigger is the risk that you won't add a call to unlock when you add a new exit point to a function.

Automatic management of locks

When you want to protect a whole block of code (a function in our case, but can be inside a loop or another control structure), it exists a good solution to avoid forgetting to release the lock: std::lock_guard.

This class is a simple smart manager for a lock. When the std::lock_guard is created, it automatically calls lock() on the mutex. When the guard gets destructed, it also releases the lock. You can use it like this:

In [ ]:
struct ConcurrentSafeCounter {
    std::mutex mutex;
    Counter counter;

    void increment(){
        std::lock_guard<std::mutex> guard(mutex);
        counter.increment();
    }

    void decrement(){
        std::lock_guard<std::mutex> guard(mutex);
        counter.decrement();
    }
};
In [ ]:
ConcurrentSafeCounter counter;
    
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i){
    threads.push_back(std::thread([&counter](){
        for(int i = 0; i < 100; ++i){
            counter.increment();
        }
    }));
}
    
for(auto& thread : threads){
    thread.join();
}
    
std::cout << counter.counter.value << std::endl;